package org.game;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.Iterator;

import org.lwjgl.BufferUtils;
import org.lwjgl.LWJGLException;
import org.lwjgl.openal.AL;
import org.lwjgl.openal.AL10;
import org.lwjgl.openal.OpenALException;
import org.newdawn.slick.openal.WaveData;

public class AudioManager {
	
	private static ArrayList loadedFiles = new ArrayList();
	private static ArrayList buffers = new ArrayList();
	private static ArrayList sources = new ArrayList();
	private static GameObject listeningObject;
	/**
	 * Create an audio manager
	 */
	public AudioManager(){
		try {
			AL.create();
		} catch (LWJGLException e) {
			e.printStackTrace();
		}
	}
	/**Create an audio manager with
	 * default files to load
	 * on start
	 * 
	 * @param audioFiles the path of files to add at start
	 */
	public AudioManager(String[] audioFiles){
		try {
			AL.create();
		} catch (LWJGLException e) {
			e.printStackTrace();
		}
		for(String path : audioFiles){
			getLoadedALBuffer(path);
		}
	}
	/**
	 * Update the audio manager
	 */
	public static void update(){
		updateListener();
	}
	/** Create a source based on a specific 
	 * set of audio data.
	 * 
	 * @param buffer the buffer of the audioData for the source to use
	 * @return the id of the source
	 */
	public static int createSource(int buffer){
		return createSource(0f,0f,0f,buffer);
	}
	/**Create a source based on a specific
	 *  set of audio data at a specific position.
	 * 
	 * @param x the x position of the source 
	 * @param y the y position of the source
	 * @param z the z position of the source 
	 * @param buffer the audio data for the source to use 
	 * @return the id of the source
	 */
	public static int createSource(float x,float y,float z,int buffer){
		return createSource(x,y,z,0f,0f,0f,buffer);
	}
	/**Create a source based on a specific
	 * set of audio data at a position and
	 * with velocity.
	 * 
	 * @param x the x position of the source 
	 * @param y the y position of the source 
	 * @param z the z position of the source
	 * @param dx the x velocity of the source 
	 * @param dy the y velocity of the source 
	 * @param dz the z velocity of the source
	 * @param buffer the audio data for the source
	 * @return the id of the source
	 */
	public static int createSource(float x,float y,float z,float dx,float dy,float dz,int buffer){
		FloatBuffer sourcePosition = BufferUtils.createFloatBuffer(3).put(new float[]{x,y,z});
		FloatBuffer sourceVelocity = BufferUtils.createFloatBuffer(3).put(new float[]{dx,dy,dz});
		IntBuffer source = BufferUtils.createIntBuffer(1);
		int result;
		
		AL10.alGenSources(source);
		
		if((result = AL10.alGetError()) != AL10.AL_NO_ERROR){
			throw new OpenALException(getALErrorString(result));
		}
		
		sourcePosition.rewind();
		sourceVelocity.rewind();
		
		AL10.alSourcei(source.get(0),AL10.AL_BUFFER,buffer);
		AL10.alSourcef(source.get(0),AL10.AL_PITCH, 1.0f);
		AL10.alSourcef(source.get(0),AL10.AL_GAIN,1.0f);
		AL10.alSource(source.get(0),AL10.AL_POSITION,sourcePosition);
		AL10.alSource(source.get(0),AL10.AL_VELOCITY,sourceVelocity);
		AL10.alSourcei(source.get(0),AL10.AL_LOOPING,AL10.AL_FALSE);
		sources.add(source.get(0));
		return source.get(0);
	}
	/**Set the position of the 
	 * source.
	 * 
	 * @param id the id of the source 
	 * @param x the x position of the source 
	 * @param y the y position of the source 
	 * @param z the z position of the source
	 */
	public static void setSourcePosition(int id, float x,float y,float z){
		FloatBuffer sourcePosition = BufferUtils.createFloatBuffer(3);
		sourcePosition.put(new float[]{x,y,z});
		sourcePosition.rewind();
		AL10.alSource(id, AL10.AL_POSITION,sourcePosition);
	}
	/**Set the velocity of a source
	 * 
	 * @param id the id of the source 
	 * @param dx the x velocity
	 * @param dy the y velocity
	 * @param dz the z velocity
	 */
	public static void setSourceVelocity(int id,float dx,float dy,float dz){
		FloatBuffer sourceVelocity = BufferUtils.createFloatBuffer(3);
		sourceVelocity.put(new float[]{dx,dy,dz});
		sourceVelocity.rewind();
		AL10.alSource(id,AL10.AL_VELOCITY,sourceVelocity);
	}
	/**Set if the source loops 
	 * its audio data
	 * 
	 * @param id the id of the source
	 * @param looping if the source loops its audio data
	 */
	public static void setSourceLooping(int id,boolean looping){
		AL10.alSourcei(id, AL10.AL_LOOPING,(looping ? AL10.AL_TRUE:AL10.AL_FALSE));
	}	
	/** Set a source's audio data
	 * 
	 * @param id the id of the source
	 * @param buffer the id of the audio data (buffer)
	 */
	public static void setSourceData(int id,int buffer){
		AL10.alSourcei(id,AL10.AL_BUFFER,buffer);
	}
	/**Play a source and it's data
	 * 
	 * @param source the id of the source to play
	 */
	public static void playSource(int source){
		AL10.alSourcePlay(source);
	}
	/**Stop a source from playing
	 * it's data
	 * 
	 * @param source the id of the source
	 */
	public static void stopSource(int source){
		AL10.alSourceStop(source);
	}
	/**Pause a source's playing
	 * 
	 * @param source the id of the source
	 */
	public static void pauseSource(int source){
		AL10.alSourcePause(source);
	}
	/**Set the audio listener of the game
	 * 
	 * @param listener the object which's position is used to receive audio data
	 */
	public static void setListeningObject(GameObject listener){
		listeningObject = listener;
	}
	/**
	 * Update the listener values
	 */
	private static void updateListener(){
		FloatBuffer position = BufferUtils.createFloatBuffer(3);
		position.put(new float[]{listeningObject.x,listeningObject.y,0});
		FloatBuffer velocity = BufferUtils.createFloatBuffer(3);
		velocity.put(new float[]{listeningObject.dx,listeningObject.dy,0});
		FloatBuffer orientation = BufferUtils.createFloatBuffer(6);
		orientation.put(new float[]{0.0f,0.0f,-1.0f,0.0f,1.0f,0.0f});
		position.rewind();
		velocity.rewind();
		orientation.rewind();
		AL10.alListener(AL10.AL_POSITION, position);
		AL10.alListener(AL10.AL_VELOCITY, velocity);
		AL10.alListener(AL10.AL_ORIENTATION,orientation);
	}
	
	/**Check if an id has an error
	 * and return the respective string
	 * 
	 * @param error the error int
	 * @return the respective string from the error
	 */
	public static String getALErrorString(int error){
		switch(error){
		case AL10.AL_NO_ERROR:
			return "AL_NO_ERROR";
		case AL10.AL_INVALID_NAME:
			return "AL_INVALID_NAME";
		case AL10.AL_INVALID_ENUM:
			return "AL_INVALID_ENUM";
		case AL10.AL_INVALID_VALUE:
			return "AL_INVALID_VALUE";
		case AL10.AL_INVALID_OPERATION:
			return "AL_INVALID_OPERATION";
		case AL10.AL_OUT_OF_MEMORY:
			return "AL_OUT_OF_MEMORY";
		default:
			return "No such error code";
		}
	}
	/** Create a buffer off of an audio file
	 * 
	 * @param path the path of the audio file 
	 * @return the buffered audio file
	 */
	public static int loadALBuffer(String path){
		int result;
		IntBuffer buffer = BufferUtils.createIntBuffer(1);
		AL10.alGenBuffers(buffer);
		if((result = AL10.alGetError()) != AL10.AL_NO_ERROR){
			throw new OpenALException(getALErrorString(result));
		}
		try {
			FileInputStream i = new FileInputStream(path);
			BufferedInputStream in = new BufferedInputStream(i);
			WaveData waveFile = WaveData.create(in);
			if(waveFile != null){
				AL10.alBufferData(buffer.get(0),waveFile.format,waveFile.data,waveFile.samplerate);
				waveFile.dispose();
			}else{
				throw new RuntimeException("No such file:"+path);
			}
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
		
		if((result = AL10.alGetError()) != AL10.AL_NO_ERROR){
			throw new OpenALException(getALErrorString(result));
		}
		return buffer.get(0);
	}
	/** Check if there is a buffer created already for 
	 * a specific file if not create the buffer for it
	 * 
	 * @param path the path of a buffer's data
	 * @return the id of the buffer
	 */
	public static int getLoadedALBuffer(String path){
		int count=0;
		for(Iterator i = loadedFiles.iterator();i.hasNext();count++){
			if(i.equals(path)){
				return ((Integer)buffers.get(count)).intValue();
			}
		}		
		int buffer = loadALBuffer(path);
		buffers.add(new Integer(buffer));;
		loadedFiles.add(path);
		return buffer;
	}
	/**
	 * Clear the list of loaded files
	 */
	public static void killAllLoadedData(){
		loadedFiles.clear();
	}
	/**
	 * Free all of the data loaded in OpenAL 
	 */
	public static void killALData(){
		IntBuffer scratch = BufferUtils.createIntBuffer(1);
		
		for(Iterator iter = buffers.iterator();iter.hasNext();){
			scratch.put(0,((Integer)iter.next()).intValue());
			AL10.alDeleteBuffers(scratch);
		}
		
		for(Iterator iter = sources.iterator();iter.hasNext();){
			scratch.put(0,((Integer)iter.next()).intValue());
		}
		
		buffers.clear();
		sources.clear();
	}
	
	public static void destroy(){
		killALData();
		AL.destroy();
	}
	
}
